package xyz.itpath.tools.tree;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author zhaom
 * <p>
 * 属性数据相关处理工具类
 */
public class QueryTreeUtils {

    /**
     * 树形查询工具返回
     *
     * @param list       包含树形关系的list
     * @param findParent 父级元素标志
     * @param findThis   当前元素标志
     * @param <T>        需要操作的对象类型
     * @return JSONArray
     */
    public static <T> JSONArray queryTree(
            List<T> list,
            Function<T, Serializable> findParent,
            Function<T, Serializable> findThis
    ) {
        return queryTree(list, findParent, findThis, ((t, jsonObject) -> jsonObject), "");
    }

    /**
     * 树形查询工具返回
     *
     * @param list       包含树形关系的list
     * @param findParent 父级元素标志
     * @param findThis   当前元素标志
     * @param addAttr    添加额外的属性
     * @param <T>        需要操作的对象类型
     * @return JSONArray
     */
    public static <T> JSONArray queryTree(
            List<T> list,
            Function<T, Serializable> findParent,
            Function<T, Serializable> findThis,
            BiFunction<T, JSONObject, JSONObject> addAttr
    ) {
        return queryTree(list, findParent, findThis, addAttr, "");
    }

    /**
     * 树形查询工具返回
     *
     * @param list       包含树形关系的list
     * @param findParent 父级元素标志
     * @param findThis   当前元素标志
     * @param addAttr    添加额外的属性
     * @param parent     是否需要指定父级节点
     * @param <T>        需要操作的对象类型
     * @return JSONArray
     */
    public static <T> JSONArray queryTree(
            List<T> list,
            Function<T, ? extends Serializable> findParent,
            Function<T, ? extends Serializable> findThis,
            BiFunction<T, JSONObject, JSONObject> addAttr,
            Serializable parent
    ) {
        JSONArray result = new JSONArray(5);
        //如果传入的父级标志为空，则先找出该列表中的顶层
        if (parent == null || "".equals(parent)) {
            getRoot(list, findParent, findThis)
                    .forEach(item -> {
                        JSONObject tem = JSONObject.parseObject(JSON.toJSONString(item));
                        JSONObject json = addAttr.apply(item, tem);
                        JSONArray tempChildren = queryTree(list, findParent, findThis, addAttr, findThis.apply(item));
                        if (tempChildren.size() > 0) {
                            json.put("children", tempChildren);
                        }
                        result.add(json);
                    });
            return result;
        } else {
            list.stream().filter(item -> findParent.apply(item) != null && findParent.apply(item).equals(parent)).forEach(item -> {
                JSONObject tem = JSONObject.parseObject(JSON.toJSONString(item));
                JSONObject json = addAttr.apply(item, tem);
                JSONArray tempChildren = queryTree(list, findParent, findThis, addAttr, findThis.apply(item));
                if (tempChildren.size() > 0) {
                    json.put("children", tempChildren);
                }
                result.add(json);
            });
            return result;
        }
    }

    /**
     * 获取自己的父节点路径
     *
     * @param list       所有数据
     * @param findThis   如何找到自己
     * @param findParent 如何找到父节点
     * @param current    自己的标识
     * @param separator  分隔符
     * @param itemField  使用哪个字段组路径
     * @param <T>
     * @return String
     */
    public static <T> String getParentPath(List<T> list,
                                           Function<T, Serializable> findThis,
                                           Function<T, Serializable> findParent,
                                           Serializable current,
                                           String separator,
                                           Function<T, String> itemField) {
        StringBuilder sb = new StringBuilder();
        Optional<T> curr = list.stream().filter(item -> current.equals(findThis.apply(item))).findAny();
        Serializable currParent = null;
        if (curr.isPresent()) {
            sb.append(separator).append(itemField.apply(curr.get()));
            currParent = findParent.apply(curr.get());
        }
        while (currParent != null && StringUtils.isNotBlank(String.valueOf(currParent))) {
            Serializable finalCurrParent = currParent;
            curr = list.stream().filter(item -> findThis.apply(item).equals(finalCurrParent)).findAny();
            if (!curr.isPresent()) {
                break;
            }
            sb.insert(0, itemField.apply(curr.get())).insert(0, separator);
            currParent = findParent.apply(curr.get());
        }
        return sb.toString();
    }

    /**
     * 获取所有的父节点id
     *
     * @param list       所有数据
     * @param findThis   如何找到自己
     * @param findParent 如何找到父节点
     * @param current    自己的标识
     */
    public static <T> List<Serializable> getAllParent(List<T> list,
                                                      Function<T, Serializable> findThis,
                                                      Function<T, Serializable> findParent,
                                                      Serializable current) {
        return getAllParent(list, findThis, findParent, current, findThis);
    }

    /**
     * 获取所有的父节点id
     *
     * @param list          所有数据
     * @param findThis      如何找到自己
     * @param findParent    如何找到父节点
     * @param current       自己的标识
     * @param getFieldValue 获取值
     * @param <T>
     * @return List
     */
    public static <T> List<Serializable> getAllParent(List<T> list,
                                                      Function<T, Serializable> findThis,
                                                      Function<T, Serializable> findParent,
                                                      Serializable current,
                                                      Function<T, Serializable> getFieldValue) {
        List<Serializable> result = new ArrayList<>();
        Optional<T> curr = list.stream().filter(item -> current.equals(findThis.apply(item))).findAny();
        Serializable currParent = null;
        if (curr.isPresent()) {
            currParent = findParent.apply(curr.get());
        }
        while (currParent != null && StringUtils.isNotBlank(String.valueOf(currParent))) {
            Serializable finalCurrParent = currParent;
            curr = list.stream().filter(item -> findThis.apply(item).equals(finalCurrParent)).findAny();
            if (!curr.isPresent()) {
                break;
            }
            result.add(getFieldValue.apply(curr.get()));
            currParent = findParent.apply(curr.get());
        }
        return result;
    }

    /**
     * 获取数据列表中指定父类标志的所有的孩子数据
     *
     * @param data       需要查询的数据集
     * @param parent     父类标志
     * @param findParent 获取孩子的标志
     * @return 如果有则返回下一层孩子的集合，如果没有返回空list
     */
    public static <T> List<T> getSons(List<T> data, Serializable parent, Function<T, Serializable> findParent) {
        return data.stream().filter(item -> findParent.apply(item).equals(parent)).collect(Collectors.toList());
    }

    /**
     * 获取数据的根节点
     *
     * @param data       需要查询的数据集
     * @param findParent 父节点标志
     * @param <T>
     * @return 所有的根节点
     */
    public static <T> List<T> getRoot(List<T> data, Function<T, ? extends Serializable> findParent, Function<T, ? extends Serializable> findThis) {
        Set<Serializable> allFlag = data.stream().map(findThis).collect(Collectors.toSet());
        return data.stream().filter(item -> !allFlag.contains(findParent.apply(item))).collect(Collectors.toList());
    }

    /**
     * 以并列的方式返回指定父类标志的所有孩子数据包括更小的层级
     *
     * @param data      需要查询的数据集
     * @param parent    父类标志
     * @param findChild 获取孩子的标志
     * @return 如果有则返回下一层孩子的集合，如果没有返回空list
     */
    public static <T> List<T> getSubTree(List<T> data, Serializable parent, Function<T, Serializable> findThis, Function<T, Serializable> findChild) {
        List<T> result = new ArrayList<>();
        List<T> sons = getSons(data, parent, findChild);
        if (CollectionUtil.isNotEmpty(sons)) {
            sons.forEach(item -> {
                result.addAll(getSons(data, findThis.apply(item), findChild));
            });
        }
        result.addAll(sons);
        return result;
    }

    /**
     * 查找出指定标志的数据在树形数据中的层级
     *
     * @param list       树形数据
     * @param findParent 获取父级标志
     * @param findThis   获取当前层级标志
     * @param sid        需要查询的标志
     * @param <T>        T
     * @return int
     */
    public static <T> int getLevel(List<T> list,
                                   Function<T, String> findParent,
                                   Function<T, String> findThis,
                                   String sid) {
        return getLevel(list, findParent, findThis, sid, 1);
    }

    /**
     * 查找出指定标志的数据在树形数据中的层级
     *
     * @param list         树形数据
     * @param findParent   获取父级标志
     * @param findThis     获取当前层级标志
     * @param sid          需要查询的标志
     * @param currentLevel 当前层级标志
     * @param <T>          T
     * @return int
     */
    private static <T> int getLevel(List<T> list,
                                    Function<T, String> findParent,
                                    Function<T, String> findThis,
                                    String sid,
                                    int currentLevel) {
        Optional<T> first = list.stream().filter(item ->
                findParent.apply(item).equals(sid)
        ).findFirst();
        return first.map(t -> getLevel(list, findParent, findThis, findThis.apply(t), currentLevel)).orElse(currentLevel);
    }

    /**
     * 查询指定的
     *
     * @param list
     * @param findParent
     * @param sid
     * @param <T>
     * @return
     */
    public static <T> boolean hadChildren(List<T> list,
                                          Function<T, String> findParent,
                                          String sid) {
        Optional<T> first = list.stream().filter(item -> findParent.apply(item).equals(sid)).findAny();
        return first.isPresent();
    }
}